/*
 * The Open Source Unix Installer
 *
 * The MIT License
 *
 * Copyright 2011-2012 Andrey Pudov.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/*
 * @author  Andrey Pudov    <andrey@andreypudov.com>
 * @version 0.00.00
 * @name    processor.c
 * @date:   Jun 17, 2012
 */

#include "processor.h"

/*
 * Identification Sequence
 *
 * To identify the processor using the CPUID instructions, software should 
 * follow the following steps.
 *
 * 1. Determine if the CPUID instruction is supported by modifying the ID flag
 * in the EFLAGS register. If the ID flag cannot be modified, the processor 
 * cannot be identified using the CPUID instruction.
 *
 * 2. Execute the CPUID instruction with EAX equal to 80000000h. CPUID function
 * 80000000h is used to determine if Brand String is supported. If the CPUID
 * function 80000000h returns a value in EAX greater than or equal to 80000004h
 * the Brand String feature is supported and software should use CPUID functions
 * 80000002h through 80000004h to identify the processor.
 *
 * 3. If the Brand String feature is not supported, execute CPUID with EAX
 * equal to 1. CPUID function 1 returns the processor signature in the EAX
 * register, and the Brand ID in the EBX register bits 0 through 7. If the EBX
 * register bits 0 through 7 contain a non-zero value, the Brand ID is 
 * supported. Software should scan the list of Brand IDs to identify the 
 * processor.
 * 
 * 4. If the Brand ID feature is not supported, software should use the 
 * processor signature in conjunction with the cache descriptors to identify 
 * the processor.
 */

/*
 * If the EAX register contains an input value of 0, the CPUID instruction
 * returns the vendor identification string in the EBX, EDX, and ECX registers.
 */

/*
 * Executing CPUID with EAX = 1 (Function 01h) cause processor identification to
 * be returned in EAX and EBX and Features Flags in ECX and EDX.
 *
 * CPUID Feature Information
 * -----------------------------------------------------------------------------
 * Register Bits
 * -----------------------------------------------------------------------------
 * EAX[31:28]    RESERVED
 * EAX[27:20]    Extended Family
 * EAX[19:16]    Extended Model
 * EAX[15:14]    RESERVED
 * EAX[13:12]    Processor Type
 * EAX[11:8]     Family
 * EAX[7:4]      Model
 * EAX[3:0]      Stepping
 * EBX[31:24]    Default APIC ID
 * EBX[23:16]    Maximum number of logical processor per package
 * EBX[15:8]     CLFLUSH line size (Value * 8 = cache line size in bytes)
 * EBX[7:0]      Brand Index
 * ECX[31:0]     Feature Flags
 * EDX[31:0]     Feature Flags
 */

/* private code declaration */
static bool _installer_processor_is_cpuid_supported();

/* public code definition */

/*
 * Returns full CPU name, like following 
 * (Intel(R) Core(TM) i5 CPU M 560  @ 2.67GHz)
 *
 * @returns the full CPU name
 */
char* installer_processor_get_name() {

	return NULL;
}

/*
 * Returns a pointer to a new string which contains CPU vendor 
 * identification string.
 *
 * @returns the CPU vendor name
 */
char* installer_processor_get_vendor() {
	/* CPUID operation code */
	u_int32_t eax = 0;

	char *vendor = (char*) calloc(13, sizeof(char));

	if (_installer_processor_is_cpuid_supported()) {
		__asm__ volatile("xorl %%eax, %%eax;"
						 "cpuid;"
						 "movl %%ebx, (%%rsi);"   /* Genu */
						 "movl %%edx, 4(%%rsi);"  /* ineI */
						 "movl %%ecx, 8(%%rsi);"  /* ntel */
						 : /* output value is not expected */
						 : "a"(eax), "m"(*vendor)
		                 : "%ebx", "%ecx", "%edx");
	}

	return vendor;
}

/*
 * Returns an integer contains CPU vendor identification value
 * from Vendor enumeration. Returns negative value If vendor 
 * identification string can not be obtainer.
 *
 * @returns the CPU vendor name
 */
int installer_processor_get_vendor_id() {
	char *vendor_string = installer_processor_get_vendor();
	int   vendor_id     = -1;
	int   hash          = -1;

	const int hash_list[][2] = {
		{("AMDisbetter!"[2] + "AMDisbetter!"[7]), AMD_IS_BETTER},
		{("AuthenticAMD"[2] + "AuthenticAMD"[7]), AUTHENTIC_AMD},
		{("CentaurHauls"[2] + "CentaurHauls"[7]), CENTAUR_HAULS},
		{("CyrixInstead"[2] + "CyrixInstead"[7]), CYRIX_INSTEAD},
		{("GenuineIntel"[2] + "GenuineIntel"[7]), GENUINE_INTEL},
		{("TransmetaCPU"[2] + "TransmetaCPU"[7]), TRAANSMETA_CPU},
		{("GenuineTMx86"[2] + "GenuineTMx86"[7]), GENUINE_TM_X86},
		{("Geode by NSC"[2] + "Geode by NSC"[7]), GEODE_BY_NSC},
		{("NexGenDriven"[2] + "NexGenDriven"[7]), NEX_GEN_DRIVEN},
		{("RiseRiseRise"[2] + "RiseRiseRise"[7]), RISE_RISE_RISE},
		{("Sis SiS SiS "[2] + "Sis SiS SiS "[7]), SIS_SIS_SIS},
		{("UMC UMC UMC "[2] + "UMC UMC UMC "[7]), UMC_UMC_UMC},
		{("VIA VIA VIA "[2] + "VIA VIA VIA "[7]), VIA_VIA_VIA},
		{("Vortex86 SoC"[2] + "Vortex86 SoC"[7]), VORTEX_86_SOC}
	};

	if (vendor_string == NULL) {
		return vendor_id;
	}

	/*
	 * hash value calculates by sum of 3rd and 8th characters
	 */
	hash = vendor_string[2] + vendor_string[7];

	for (unsigned int index = 0; index < (sizeof hash_list / sizeof(int) / 2); ++index) {
		if (hash == hash_list[index][0]) {
			vendor_id = (int) hash_list[index][1];
			break;
		}
	}

	free(vendor_string);

	return vendor_id;
}

/*
 * Returns a CPU stepping value.
 * 
 * @returns the CPU stepping value or negative value if value is not reachable.
 */
int installer_processor_get_stepping() {
	/* CPUID operation code */
	u_int32_t eax = 1;
	u_int32_t ecx = 2;

	if (_installer_processor_is_cpuid_supported()) {
		__asm__ volatile("cpuid"
						 : "=a"(eax)
						 : "a"(eax), "c"(ecx)
					     : "%ebx", "%edx");

		return (eax & 0xF);
	}

	return -1;
}

/*
 * Returns a CPU model value.
 * 
 * @returns the CPU model value or negative value if value is not reachable.
 */
int installer_processor_get_model() {
	/* CPUID operation code */
	u_int32_t eax = 1;

	if (_installer_processor_is_cpuid_supported()) {
		/*
		 * M = (Extended Model << 4) + Model
		 * M = (CPUID(1).EAX[19:16] << 4) + CPUID(1).EAX[7:4]
		 */
		__asm__ volatile("cpuid"
						 : "=a"(eax)
						 : "a"(eax)
		                 : "%ebx", "%ecx", "%edx");

		return (((eax >> 12)) + ((eax >> 4) & 0xF));
	}

	return -1;
}

/*
 * Returns a CPU family value.
 * 
 * @returns the CPU family value or negative value if value is not reachable.
 */
int installer_processor_get_family() {
	/* CPUID operation code */
	u_int32_t eax = 1;

	if (_installer_processor_is_cpuid_supported()) {
		/* 
		 * F = Extended Family + Family 
		 * F = CPUID(1).EAX[27:20] + CPUID(1).EAX[11:8]
		 */
		__asm__ volatile("cpuid"
						 : "=a"(eax)
						 : "a"(eax)
		                 : "%ebx", "%ecx", "%edx");

		return (((eax >> 20)) + ((eax >> 8) & 0xF));
	}

	return -1;
}

/*
 * Returns new allocated array of supported flags. Flags values are stored in 
 * Flags enumeration. Returned array is fixed-size array of integers contains
 * flag-oriented values - Flag-value for supported flag and zero values
 * for unsopported uninitialized flags.
 *
 * @returns the array of supported flags.
 */
int*  installer_processor_get_flags() {
	int *flags = (int*) calloc(64, sizeof(int));

	/* CPUID operation code */
	u_int32_t eax  = 1;
	u_int32_t edx  = 0;
	u_int32_t ecx  = 0;

    if (_installer_processor_is_cpuid_supported()) {
		__asm__ volatile("cpuid"
						 : "=c"(ecx), "=d"(edx)
						 : "a"(eax));

        
        for (unsigned int index = 0; index < 32; ++index) {
			/* ECX feauture flags */
			flags[index] = ((ecx >> index) & 0xF);

			/* EDX feauture flags */
			flags[index + 32] = ((edx >> index) & 0xF);
        }
	}

	return flags;
}

/* private code definition */

/*
 * Determine if the CPUID instruction is supported.
 *
 * @returns true if CPUID instruction is supported, false otherwise.
 */
bool _installer_processor_is_cpuid_supported() {
	/* x86 - pushfl/popfl, x86_64 pushfq/popfq */
	u_int32_t is_enabled = 0;

	/* returns 1 if CPUID is supported, 0 otherwise (ZF set accordingly) */
    __asm__ volatile("pushfq;"
                     "pop %%rax;"
					 "movq %%rax, %%rcx;"      /* get original EFLAGS */
					 "xor $0x200000, %%rax;"   /* flip ID bit in EFLAGS */
					 "push %%rax;"             /* save new EFLAGS value on stack */
					 "popfq;"                  /* replace current EFLAGS value */
					 "pushfq;"                 /* get new EFLAGS */
					 "pop %%rax;"              /* store new EFLAGS in EAX */
					 "xor %%rcx, %%rax;"       /* mask changed bit */
					 "shr $21, %%rax;"         /* move bit 21 to bit 0 */
					 "and $1, %%rax;"          /* mask others */
					 "push %%rcx;"
					 "popfq;"                  /* restore EFLAGS */
					 : "=a"(is_enabled)
					 );

	return is_enabled;
}
